
import { useLocalForage } from '@/hooks/useLocalForage';
import { getLoginInfo } from '@/hooks/useLoginInfo';
import { Paths } from '@/paths';
import localforage from 'localforage';
import QueryString from 'qs';
import type { Tab } from 'rc-tabs/lib/interface';
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
import { useLocation } from "react-router";

export interface NavTabInfo {
  key: string;
  pathname: string;
  search: string;
  hash: string;
  state: any;
  name?: string;
}
export interface RemoveTabFunc {
  (key?: string): void;
}
export interface AddTabFunc {
  (key: string): void;
}
export const NavTabsContext = createContext<ReturnType<typeof useNavTabs>>({
  tabs: [],
  remove() { },
  removeAll() { },
  activeKey: '',
  setActiveKey() { return false },
  reorder() { },
  removeToTheLeft() { },
  removeToTheRight() { },
  removeOthers() { },
  editTab() { },
})

export function useNavTabsVisitor() {
  return useContext(NavTabsContext)
}
export async function getNavTabs(): Promise<NavTabInfo[]> {
  const storage = localforage.createInstance({
    name: 'open_tab_infos'
  })
  try {
    const v = await storage.getItem<NavTabInfo[]>("tab_infos");
    return v || [];
  } catch (e) {
    console.error(e);
    return [];
  }
}

export function gen_tab_key(pathname: string, search: string, hash: string): string {
  const fn = Paths.NavTabKeys[pathname as Paths.All] ?? Paths.defaultNavTabKey;
  return fn(pathname, search, hash)
}

export interface IUseNavTabsOptions {
  /**
   * 是否检查路由关系
   * 
   * 默认情况下，若当前路由不存在于Paths.All.Main下，将离开当前路由
   *
   * @type {boolean} 默认值 false
   * @memberof IUseNavTabsOptions
   */
  dont_checked?: boolean;
}

export function useNavTabs(navNames: { [key in string]?: string }, opts: IUseNavTabsOptions = {}) {
  const { dont_checked = false } = opts;
  const has_permission = Paths.usePermission();
  const navigate = Paths.useNavigate();
  const { pathname, search, state, hash } = useLocation();
  const ref_prev_pathname = useRef<typeof pathname | undefined>(void 0);
  const ref_prev_search = useRef<typeof search | undefined>(void 0);
  const ref_prev_hash = useRef<typeof hash | undefined>(void 0);

  const [tab_infos, __set_tab_infos] = useState<NavTabInfo[]>()
  const [latest_tab_key, __set_latest_tab_key] = useState<string | null>(null)
  const [storage] = useLocalForage({ name: 'open_tab_infos', storeName: '' + getLoginInfo('userId') })
  useEffect(() => {
    storage.getItem<NavTabInfo[]>("tab_infos").then((tab_infos) => {
      __set_tab_infos(tab_infos?.filter(v => has_permission(v.pathname)) ?? []);
    }).catch(e => {
      __set_tab_infos([]);
      console.error(e);
    })
    storage.getItem<string>('latest_tab_key').then((latest_tab_key) => {
      __set_latest_tab_key(latest_tab_key)
    }).catch(e => {
      console.error(e);
    })
  }, [storage, has_permission])

  const set_tabs = useCallback((tab_infos: NavTabInfo[]) => {
    __set_tab_infos(tab_infos)
    storage.setItem('tab_infos', tab_infos);
  }, [storage, __set_tab_infos]);

  const edit_tabs = useCallback((fn: (tabInfos: NavTabInfo[]) => NavTabInfo[]) => {
    __set_tab_infos(prev => {
      const next = fn(prev || []);
      storage.setItem('tab_infos', next);
      return next;
    })
  }, [tab_infos, set_tabs])

  const editTab = useCallback((key: undefined | null | string, fn: (tab: NavTabInfo) => NavTabInfo) => {
    key = key || latest_tab_key
    if (!tab_infos) return;
    const next_tabs = [...tab_infos];
    const idx = next_tabs.findIndex(v => v.key === key);
    if (idx < 0) return;
    next_tabs[idx] = fn(next_tabs[idx])
    set_tabs(next_tabs)
  }, [latest_tab_key, edit_tabs, navigate]);

  const set_latest_tab_key = async (v: string | null) => {
    __set_latest_tab_key(v);
    if (!v) {
      return storage.removeItem('latest_tab_key');
    } else {
      return storage.setItem('latest_tab_key', v);
    }
  }

  const ref_has_inited = useRef(false);

  const reorder = (keys: string[]) => {
    const newTabInfos: NavTabInfo[] = []
    for (let i = 0; i < keys.length; ++i) {
      const key = keys[i];
      const tabInfo = tab_infos?.find(v => v.key === key);
      if (tabInfo) newTabInfos.push(tabInfo);
    }
    set_tabs(newTabInfos);
  }

  const remove: RemoveTabFunc = (key = latest_tab_key ?? '') => {
    if (!tab_infos) { return; }
    const idx = tab_infos.findIndex(v => v.key === key);
    if (idx < 0) { return; }
    if (latest_tab_key === tab_infos[idx].key) {
      const tabInfo = tab_infos[idx - 1] ?? tab_infos[idx + 1];
      if (tabInfo) {
        const interrupted = navigate(tabInfo.pathname + tabInfo.search + tabInfo.hash, { state: tabInfo.state });
        if (interrupted) return;
      }
    }
    const next_tab_infos = tab_infos.filter(v => v.key !== key)
    next_tab_infos.length || navigate(Paths.All.Main)
    set_tabs(next_tab_infos)
  }

  const add = (pathname: string, search: string, hash: string, state: any) => {
    const tab_info = {
      key: gen_tab_key(pathname, search, hash),
      pathname,
      search,
      hash,
      state,
    }
    if (!tab_infos) {
      set_tabs([tab_info])
      return
    }
    const tab_info_idx = tab_infos.findIndex(
      item => gen_tab_key(pathname, search, hash) === gen_tab_key(item.pathname, item.search, item.hash)
    )
    if (tab_info_idx < 0) {
      const insert_pos = tab_infos.findIndex(item =>
        item.pathname === ref_prev_pathname.current &&
        item.search === ref_prev_search.current &&
        item.hash === ref_prev_hash.current
      )
      if (insert_pos < 0)
        return set_tabs([...tab_infos, tab_info])
      const next_tab_infos = [...tab_infos]
      next_tab_infos.splice(insert_pos + 1, 0, tab_info)
      set_tabs(next_tab_infos)
    } else {
      const next_tab_infos = [...tab_infos]
      next_tab_infos[tab_info_idx] = tab_info;
      set_tabs(next_tab_infos)
    }
  }

  const removeAll = () => {
    set_tabs([]);
    navigate(Paths.All.Main);
  }

  const removeToTheLeft = (key: string) => {
    if (!tab_infos) { return }

    const curIdx = tab_infos.findIndex(v => v.key === latest_tab_key)
    const idx = tab_infos.findIndex(v => v.key === key)
    if (idx < 0) { return }
    const arr = tab_infos.splice(idx, tab_infos.length - idx);
    set_tabs(arr);
    if (curIdx < idx) {
      const tabInfo = arr[0];
      navigate(tabInfo.pathname + tabInfo.search + tabInfo.hash, { state: tabInfo.state });
    }
  }

  const removeToTheRight = (key: string) => {
    if (!tab_infos) { return }

    const curIdx = tab_infos.findIndex(v => v.key === latest_tab_key)
    const idx = tab_infos.findIndex(v => v.key === key)
    if (idx < 0) { return }
    const arr = tab_infos.splice(0, idx + 1);
    set_tabs(arr);
    if (curIdx > idx) {
      const tabInfo = arr[arr.length - 1];
      navigate(tabInfo.pathname + tabInfo.search + tabInfo.hash, { state: tabInfo.state });
    }
  }

  const removeOthers = (key: string) => {
    const tabInfo = tab_infos?.find(v => v.key === key)
    if (!tabInfo) { return; }

    set_tabs([tabInfo]);
    navigate(tabInfo.pathname + tabInfo.search + tabInfo.hash, { state: tabInfo.state });
  }

  const tabs = tab_infos?.map<Tab>(v => {
    const { key, pathname, search, name, hash, state } = v;
    const { nav_tab_title: ntt_0 } = QueryString.parse(search, { ignoreQueryPrefix: true });
    const { nav_tab_title: ntt_1 } = QueryString.parse(hash, { ignoreQueryPrefix: true });
    const { nav_tab_title: ntt_2 } = state || {}
    const label = ntt_0 || ntt_1 || ntt_2 || name || navNames[pathname] || key;
    return { key, label }
  }) || [];

  useEffect(() => {
    if (dont_checked) return;
    if (!tab_infos) return;
    do {
      if (Paths.Relations[Paths.All.Main]!.indexOf(pathname as Paths.All) >= 0) {
        add(pathname, search, hash, state)
        set_latest_tab_key(gen_tab_key(pathname, search, hash))
        break;
      }
      const tab_info = tab_infos?.find(v => v.key === latest_tab_key)
      if (latest_tab_key && tab_info) {
        navigate(tab_info.pathname + tab_info.search + tab_info.hash, { replace: true })
        break;
      }
      if (tab_infos?.length) {
        const { pathname, search, hash, state } = tab_infos[tab_infos.length - 1]
        navigate(pathname + search + hash, { state, replace: true });
        break;
      }
      if (!ref_has_inited.current) {
        navigate(Paths.All.Main, { replace: true })
        break;
      }
    } while (0)
    ref_prev_pathname.current = pathname
    ref_prev_search.current = search
    ref_prev_hash.current = hash
    ref_has_inited.current = true;
  }, [pathname, search, hash, state, !!tab_infos, dont_checked])

  const setActiveKey = (key: string) => {
    const info = tab_infos?.find(v => v.key === key)
    if (!info) navigate(key)
    else navigate(info.pathname + info.search + info.hash, { state: info.state })
  };

  return {
    tabs,
    remove,
    editTab,
    removeAll,
    activeKey: gen_tab_key(pathname, search, hash),
    setActiveKey,
    reorder,
    removeToTheLeft,
    removeToTheRight,
    removeOthers
  }
}